﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace PFAGame
{
    public abstract class AMob : AObject
    {
        internal enum e_typeMob { KAMIKAZ, KAMIKAZBOSS, MELEE, MELEEBOSS, RANGED, RANGEDBOSS, TOWER, BISHOP, QUEEN, KNIGHT };
        internal e_typeMob type;

        internal enum e_state { SEEK, ATRANGE, BUMPED, DYING };
        internal e_state state = e_state.SEEK;

        public Vector2 bumpDirection;
        public float bumpPower;
        public float bumpDuration;
        protected float bumpTime;

        protected APlayer target;
        bool isAttacking = false;
        float attackTime = 0f;
        float dieTime = 0f;
        bool attackDone;

        protected float speed = 1.5f;                   // vitesse de deplcacement, case par seconde
        protected float range = 1f;                     // porté
        protected float dammage = 8;                    // dégat infligé
        protected float life = 100;
        protected int exp = 5;

        protected int Exp { get { return exp; } }

        protected float attackAnimationDuration = 1f;     // durée de l'animation d'attaque
        protected float dieAnimationDuration = 1f;        // durée de l'animation d'attaque

        static Random random = new Random();
        private float lootChance = 20f;
        public float LootChance
        {
            get { return lootChance; }
            set { lootChance = value; }
        }

        // gestion des animation
        protected Animation moveRightAnim = new Animation();
        protected Animation moveUpAnim = new Animation();
        protected Animation moveDownAnim = new Animation();
        protected Animation moveLeftAnim = new Animation();
        protected Animation attackUpAnim = new Animation();
        protected Animation attackRightAnim = new Animation();
        protected Animation attackDownAnim = new Animation();
        protected Animation attackLeftAnim = new Animation();

        protected Color color = Color.White;
        protected float scale = 1.0f;

        public override void init()
        {
            base.init();
        }

        public virtual void bumpedUpdate(GameTime gameTime)
        {
            float deltaTime = (float)gameTime.ElapsedGameTime.Ticks / 10000000;
            bumpTime += deltaTime;

            Vector2 tmpPosition;
            //test la colision indépendament sur chaqu'un des axes pour permettre de glisser sur les cases bloquantes
            if (GameplayScreen.Singleton.CurrentType == 1) //on ne peut pas monter en vue de platform
            {
                tmpPosition = position + bumpDirection * bumpPower * ((float)gameTime.ElapsedGameTime.Ticks / 10000000);
                tmpPosition.X = position.X;
                if (!GameplayScreen.Singleton.MapP.checkCollision(tmpPosition))
                    position = tmpPosition;
            }

            tmpPosition = position + bumpDirection * bumpPower * ((float)gameTime.ElapsedGameTime.Ticks / 10000000);
            tmpPosition.Y = position.Y;
            if (!GameplayScreen.Singleton.MapP.checkCollision(tmpPosition))
                position = tmpPosition;

            if (bumpTime > bumpDuration)
                state = e_state.SEEK;
        }

        public override  void update(GameTime gameTime)
        {
            base.update(gameTime);

            // mob out of range
                if (getSquaredDistance(GameplayScreen.Singleton.Players[0].Position) > squaredMobActivationRange)
                {
                    GameplayScreen.Singleton.SleepMobList.Add(this);
                    GameplayScreen.Singleton.DeleteList.Add(this);
                    return;
                }

            // switch state machine
            switch (state)
            {
                case e_state.SEEK:      seekUpdate(gameTime);       break;
                case e_state.ATRANGE:   atRangeUpdate(gameTime);    break;
                case e_state.BUMPED:    bumpedUpdate(gameTime);     break;
                case e_state.DYING:     dyingUpdate(gameTime);      break;
            }

            // update anim
            if (!isAttacking)
            {
                moveRightAnim.Update(gameTime);
                moveUpAnim.Update(gameTime);
                moveDownAnim.Update(gameTime);
                moveLeftAnim.Update(gameTime);
            }
            else
            {
                attackUpAnim.Update(gameTime);
                attackRightAnim.Update(gameTime);
                attackDownAnim.Update(gameTime);
                attackLeftAnim.Update(gameTime);
            }
        }

        public void receiveBump(Vector2 direction, float power, float time)
        {
            if (state == e_state.DYING)
                return;

            bumpDirection = direction;
            bumpPower = power;
            bumpTime = 0;
            bumpDuration = time;
            state = e_state.BUMPED;
        }

        public void receiveDammage(float dammage)
        {
            life -= dammage;
            if (life < 0)
                state = e_state.DYING;
        }

        public virtual void seekUpdate(GameTime gameTime)
        {
            orientation = GameplayScreen.Singleton.MapP.getDirectionFromPathfinding(position);
            direction = orientation * speed;
            position += direction * gameTime.ElapsedGameTime.Ticks / 10000000;

            float distanceSquared;
            target = APlayer.getClosestPlayer(position, out distanceSquared);
            if (distanceSquared <= range * range * 0.8f)
            {
                state = e_state.ATRANGE;
                isAttacking = false;
            }
        }

        protected abstract void attack();

        public virtual void atRangeUpdate(GameTime gameTime)
        {
            if (GameplayScreen.Singleton.NetworkSessionP != null && GameplayScreen.Singleton.NetworkSessionP.IsHost == false)
                return;

            if (isAttacking)
            {
                // continue l'attaque
                attackTime += (float)gameTime.ElapsedGameTime.Ticks / 10000000;
                if (!attackDone && attackTime / attackAnimationDuration >= 0.5f) // demi-animation
                {
                    attack();
                    attackDone = true;
                }
                else if (attackTime / attackAnimationDuration >= 1f) // attaque terminé
                    isAttacking = false;
            }
            else if (target.getSquaredDistance(position) > range * range)
            {
                // target out of range
                state = e_state.SEEK;
                return;
            }
            else
            {
                // début d'attaque
                isAttacking = true;
                attackDone = false;
                attackTime = 0;

                attackLeftAnim.reset();
                attackDownAnim.reset();
                attackRightAnim.reset();
                attackUpAnim.reset();
            }
        }

        public virtual void dyingUpdate(GameTime gameTime)
        {
            if (GameplayScreen.Singleton.NetworkSessionP != null && GameplayScreen.Singleton.NetworkSessionP.IsHost == false)
                return;

            dieTime += (float)gameTime.ElapsedGameTime.Ticks / 10000000;
            if (dieTime > dieAnimationDuration)
            {
                int rand = random.Next(100);
                if (rand < lootChance)
                {
                    ALoot loot;
                    // TODO: ajouter tout les type de loot
                    switch (random.Next(3))
                    {
                        case 0: loot = new Heal(); break;
                        case 1: loot = new Armor(); break;
                        case 2: loot = new Berserker(); break;
                        default: loot = new Heal(); break;
                    }
                    loot.init();
                    loot.Position = position;
                    GameplayScreen.Singleton.LootList.Add(loot);
                }
                foreach (APlayer player in GameplayScreen.Singleton.Players)
                    player.receiveExp(exp);
                GameplayScreen.Singleton.DeleteList.Add(this);
            }
        }

        #region static

        private const int refreshFrequency = 3000;
        private static int currentRefresh = 0;
        private const int squaredMobActivationRange = 625;

        /// <summary>
        /// prend une liste de mob et retourne la list des mob à porté d'activation
        /// </summary>
        /// <param name="mobList">liste de mob</param>
        /// <param name="cameraPosition"> position de référence</param>
        /// <returns></returns>
        private static List<AMob> getMobsAtRange(List<AMob> mobList, Vector2 cameraPosition)
        {
            List<AMob> list = new List<AMob>();
            foreach (AMob mob in mobList)
            {
                Vector2 distance = mob.position - cameraPosition;
                if (distance.LengthSquared() < squaredMobActivationRange)
                    list.Add(mob);
            }
            return list;
        }

        /// <summary>
        /// met tout les monstre à porté depuis la liste de monstre dormant vers la liste de monstre actif et appel init
        /// </summary>
        /// <param name="mobList"> liste de mob actif</param>
        /// <param name="sleepMobList"> liste de mob dormant</param>
        /// <param name="cameraPosition"> position de référence</param>
        /// <param name="gameTime"> temps</param>
        internal static void swapMobFromList(List<AMob> mobList, List<AMob> sleepMobList, Vector2 cameraPosition, GameTime gameTime)
        {
            currentRefresh += gameTime.ElapsedGameTime.Milliseconds;
            if (currentRefresh > refreshFrequency)
                currentRefresh = 0;
            else
                return;

            List<AMob> list = getMobsAtRange(sleepMobList, cameraPosition);
            foreach (AMob mob in list)
            {
                mobList.Add(mob);
                mob.init();
                sleepMobList.Remove(mob);
            }

        }


        public override void draw(GameTime gameTime, Camera camera)
        {
            if (!camera.isInFrustum(position))
                return;

            base.draw(gameTime, camera);

            Animation anim;
            //choix de la direction
            if (Math.Abs(orientation.X) > Math.Abs(orientation.Y) || GameplayScreen.Singleton.CurrentType != 1)
            {
                // ici droite ou gauche
                if (!isAttacking)
                    anim = orientation.X > 0 ? moveRightAnim : moveLeftAnim;
                else
                    anim = orientation.X > 0 ? attackRightAnim : attackLeftAnim;
            }
            else
            {
                // ici haut ou bas
                if (!isAttacking)
                    anim = orientation.Y > 0 ? moveUpAnim : moveDownAnim;
                else
                    anim = orientation.Y > 0 ? attackUpAnim : attackDownAnim;
            }

            // on multiplie par 2 la taille du mob:
            displayRectangle.X -= (int)(camera.ZoomLevel * scale);
            displayRectangle.Y -= (int)(camera.ZoomLevel * scale);
            displayRectangle.Width = (int)(displayRectangle.Width * scale * 2);
            displayRectangle.Height = (int)(displayRectangle.Height * scale * 2);

            anim.Draw(ScreenManager.Singleton.SpriteBatch, displayRectangle, color);
        }

        /// <summary>
        /// met tout les monstre à porté depuis la liste de monstre dormant vers la liste de monstre actif et appel init. surcharge sans time
        /// </summary>
        /// <param name="mobList"> liste de mob actif</param>
        /// <param name="sleepMobList"> liste de mob dormant</param>
        /// <param name="cameraPosition"> position de référence</param>
        internal static void swapMobFromList(List<AMob> mobList, List<AMob> sleepMobList, Vector2 cameraPosition)
        {
            List<AMob> list = getMobsAtRange(sleepMobList, cameraPosition);
            foreach (AMob mob in list)
            {
                mobList.Add(mob);
                mob.init();
                sleepMobList.Remove(mob);
            }

        }

        #endregion
    }
}
